/* eslint-disable jsdoc/require-returns */
/* eslint-disable jsdoc/require-param */

/**
 * A helper interface as an extension reference for user-provided properties of
 * nodes and links in the graph, which are not required or calculated by
 * the Sankey layout Generator
 */
export interface SankeyExtraProperties {
  [key: string]: any;
}

/**
 * Helper interface to define the properties of Sankey Nodes. Calculated properties may only be defined,
 * once the layout(...) method of the Sankey layout generator has been invoked.
 *
 * Those properties correspond to a SankyNode generated by `sankey(withoutPositions=true)`.
 *
 * The first generic N refers to user-defined properties contained in the node data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyNodeStrictMinimal interface.
 *
 * The second generic L refers to user-defined properties contained in the link data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyLinkStrictMinimal interface.
 */
export interface SankeyNodeStrictMinimal<
  N extends SankeyExtraProperties,
  L extends SankeyExtraProperties,
> {
  /**
   * Array of outgoing links which have this node as their source.
   * This property is calculated internally by the Sankey layout generator.
   */
  sourceLinks?: Array<SankeyLinkStrictMinimal<N, L>> | undefined;
  /**
   * Array of incoming links which have this node as their target.
   * This property is calculated internally by the Sankey layout generator.
   */
  targetLinks?: Array<SankeyLinkStrictMinimal<N, L>> | undefined;
  /**
   * Node's value calculated by Sankey layout Generator;
   * the sum of link.value for the node's incoming links.
   */
  value?: number | undefined;
  /**
   * Node's fixedValue (user-defined)
   */
  fixedValue?: number | undefined;
  /**
   * Node's zero-based index within the array of nodes calculated by Sankey layout generator.
   */
  index?: number | undefined;
  /**
   * Node's zero-based graph depth, derived from the graph topology calculated by Sankey layout generator.
   */
  depth?: number | undefined;
  /**
   * Node's zero-based graph height, derived from the graph topology calculated by Sankey layout generator.
   */
  height?: number | undefined;
  /**
   * Link's layer in case of layered layout, calculated by Sankey layout generator.
   */
  layer?: number | undefined;
}

interface SankeyNodePosition {
  /**
   * Node's minimum horizontal position (derived from the node.depth) calculated by Sankey layout generator.
   */
  x0?: number | undefined;
  /**
   * Node's maximum horizontal position (node.x0 + sankey.nodeWidth) calculated by Sankey layout generator.
   */
  x1?: number | undefined;
  /**
   * Node's minimum vertical position calculated by Sankey layout generator.
   */
  y0?: number | undefined;
  /**
   * Node's maximum vertical position (node.y1 - node.y0 is proportional to node.value) calculated by Sankey layout generator.
   */
  y1?: number | undefined;
}

/**
 * Helper interface to define the properties of Sankey Nodes. Calculated properties may only be defined,
 * once the layout(...) method of the Sankey layout generator has been invoked.
 *
 * Those properties correspond to a SankyNode generated by `sankey(withoutPositions=false)`.
 *
 * The first generic N refers to user-defined properties contained in the node data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyNodeMinimal interface.
 *
 * The second generic L refers to user-defined properties contained in the link data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyLinkMinimal interface.
 */
export interface SankeyNodeMinimal<N extends SankeyExtraProperties, L extends SankeyExtraProperties>
  extends SankeyNodeStrictMinimal<N & SankeyNode<true, N, L>, L & SankeyLink<true, N, L>>,
    SankeyNodePosition {}

/**
 * Sankey Node type including both user-defined node data elements as well as those
 * calculated once the layout(...) method of the Sankey layout generators has been invoked.
 *
 * The first generic N refers to user-defined properties contained in the node data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyNodeMinimal interface.
 *
 * The second generic L refers to user-defined properties contained in the link data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyLinkMinimal interface.
 */
export type SankeyNode<
  WithPosition extends boolean,
  N extends SankeyExtraProperties,
  L extends SankeyExtraProperties,
> = N & (WithPosition extends true ? SankeyNodeMinimal<N, L> : SankeyNodeStrictMinimal<N, L>);

/**
 * Helper interface to define the properties of Sankey Links. Calculated properties may only be defined,
 * once the layout(...) method of the Sankey layout generator has been invoked.
 *
 * Those properties correspond to a SankyLink generated by `sankey(withoutPositions=true)`.
 *
 * The first generic N refers to user-defined properties contained in the node data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyNodeMinimal interface.
 *
 * The second generic L refers to user-defined properties contained in the link data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyLinkMinimal interface.
 */
export interface SankeyLinkStrictMinimal<
  N extends SankeyExtraProperties,
  L extends SankeyExtraProperties,
> {
  /**
   * Link's source node. For convenience, when initializing a Sankey layout using the default node id accessor,
   * source may be the zero-based index of the corresponding node in the nodes array
   * returned by the nodes accessor of the Sankey layout generator rather than object references. Alternatively,
   * the Sankey layout can be configured with a custom node ID accessor to resolve the source node of the link upon initialization.
   *
   * Once the Sankey generator is invoked to return the Sankey graph object,
   * the numeric index will be replaced with the corresponding source node object.
   */
  source: number | string | SankeyNodeStrictMinimal<N, L>;
  /**
   * Link's target node. For convenience, when initializing a Sankey layout using the default node id accessor,
   * target may be the zero-based index of the corresponding node in the nodes array
   * returned by the nodes accessor of the Sankey layout generator rather than object references. Alternatively,
   * the Sankey layout can be configured with a custom node ID accessor to resolve the target node of the link upon initialization.
   *
   * Once the Sankey generator is invoked to return the Sankey graph object,
   * the numeric index will be replaced with the corresponding target node object.
   */
  target: number | string | SankeyNodeStrictMinimal<N, L>;
  /**
   * Link's numeric value
   */
  value: number;
  /**
   * Link's zero-based index within the array of links calculated by Sankey layout generator.
   */
  index?: number | undefined;
}

interface SankeyLinkPosition {
  /**
   * Link's vertical starting position (at source node) calculated by Sankey layout generator.
   */
  y0?: number | undefined;
  /**
   * Link's vertical end position (at target node) calculated by Sankey layout generator.
   */
  y1?: number | undefined;
  /**
   * Link's width (proportional to its value) calculated by Sankey layout generator.
   */
  width?: number | undefined;
}

/**
 * Helper interface to define the properties of Sankey Links. Calculated properties may only be defined,
 * once the layout(...) method of the Sankey layout generator has been invoked.
 *
 * The first generic N refers to user-defined properties contained in the node data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyNodeMinimal interface.
 *
 * The second generic L refers to user-defined properties contained in the link data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyLinkMinimal interface.
 */
export interface SankeyLinkMinimal<N extends SankeyExtraProperties, L extends SankeyExtraProperties>
  extends SankeyLinkStrictMinimal<N & SankeyNode<true, N, L>, L & SankeyLink<true, N, L>>,
    SankeyLinkPosition {}

/**
 * Sankey Link type including both user-defined link data elements, those required by the Sankey layout generator,
 *  as well as those calculated once the layout(...) method of the layout generator has been invoked.
 *
 * The first generic N refers to user-defined properties contained in the node data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyNodeMinimal interface.
 *
 * The second generic L refers to user-defined properties contained in the link data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyLinkMinimal interface.
 */
export type SankeyLink<
  WithPosition extends boolean,
  N extends SankeyExtraProperties,
  L extends SankeyExtraProperties,
> = L & (WithPosition extends true ? SankeyLinkMinimal<N, L> : SankeyLinkStrictMinimal<N, L>);

/**
 * A Sankey Graph Object which contains the computed layout information for nodes and links.
 *
 * The first generic N refers to user-defined properties contained in the node data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyNodeMinimal interface.
 *
 * The second generic L refers to user-defined properties contained in the link data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyLinkMinimal interface.
 */
export interface SankeyGraph<
  WithPosition extends boolean,
  N extends SankeyExtraProperties,
  L extends SankeyExtraProperties,
> {
  /**
   * Array of Sankey diagram nodes
   */
  nodes: Array<SankeyNode<WithPosition, N, L>>;
  /**
   * Array of Sankey diagram links
   */
  links: Array<SankeyLink<WithPosition, N, L>>;
}

/**
 * A Sankey layout generator.
 *
 * The first generic Data refers to the data type of the first argument passed in when invoking the
 * Sankey layout generator and internally the configured nodes/links accessor functions.
 *
 * The second generic N refers to user-defined properties contained in the node data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyNodeMinimal interface.
 *
 * The third generic L refers to user-defined properties contained in the link data passed into
 * Sankey layout generator. These properties are IN EXCESS to the properties explicitly identified in the
 * SankeyLinkMinimal interface.
 */
export interface SankeyLayout<
  WithPosition extends boolean,
  Data,
  N extends SankeyExtraProperties,
  L extends SankeyExtraProperties,
> {
  /**
   * Computes the node and link positions for the given arguments, returning a graph representing the Sankey layout.
   *
   * @param data Data object being passed as the first argument to the nodes and links accessor functions. Additional arguments will also be passed
   * to the accessor functions.
   */
  (data: Data, ...args: any[]): SankeyGraph<WithPosition, N, L>;

  /**
   * Recomputes the specified graph's links' positions, updating the following properties of each link:
   *
   * - link.sy: the link's vertical starting position (at source node)
   * - link.ty: the link's vertical end position (at target node)
   *
   * This method is intended to be called after computing the initial Sankey layout, for example when the diagram is repositioned interactively.
   *
   * @param graph A previously initialized Sankey graph for which the link positions should be re-calculated
   */
  update(graph: SankeyGraph<WithPosition, N, L>): SankeyGraph<WithPosition, N, L>;

  /**
   * Return the current nodes accessor function, which defaults to a function returning the "nodes" property of the
   * first argument it is invoked with.
   */
  nodes(): (data: Data, ...args: any[]) => Array<SankeyNode<WithPosition, N, L>>;
  /**
   * Set the Sankey generator's nodes accessor to a function returning the specified array of objects and returns this Sankey layout generator.
   *
   * @param nodes Array of nodes.
   */
  nodes(nodes: Array<SankeyNode<WithPosition, N, L>>): this;
  /**
   * Set the Sankey generator's nodes accessor to the specified function and returns this Sankey layout generator.
   *
   * @param nodes A nodes accessor function. The function is invoked when the Sankey layout is generated, being passed any arguments passed to the Sankey generator.
   * This function must return an array of nodes.
   */
  nodes(nodes: (data: Data, ...args: any[]) => Array<SankeyNode<WithPosition, N, L>>): this;

  /**
   * Return the current links accessor function, which defaults to a function returning the "links" property of the
   * first argument it is invoked with.
   */
  links(): (data: Data, ...args: any[]) => Array<SankeyLink<WithPosition, N, L>>;
  /**
   * Set the Sankey generator's links accessor to a function returning the specified array of objects and returns this Sankey layout generator.
   *
   * @param links Array of links.
   */
  links(links: Array<SankeyLink<WithPosition, N, L>>): this;
  /**
   * Set the Sankey generator's links accessor to the specified function and returns this Sankey layout generator.
   *
   * @param links A links accessor function. The function is invoked when the Sankey layout is generated, being passed any arguments passed to the Sankey generator.
   * This function must return an array of links.
   */
  links(links: (data: Data, ...args: any[]) => Array<SankeyLink<WithPosition, N, L>>): this;

  /**
   * Return the current node id accessor.
   * The default accessor is a function being passed in a Sankey layout node and returning its numeric node.index.
   */
  nodeId(): (node: SankeyNode<WithPosition, N, L>) => string | number;
  /**
   * Set the node id accessor to the specified function and return this Sankey layout generator.
   *
   * The default accessor is a function being passed in a Sankey layout node and returning its numeric node.index.
   * The default id accessor allows each link's source and target to be specified as a zero-based index into the nodes array.
   *
   * @param nodeId A node id accessor function being passed a node in the Sankey graph and returning its id.
   */
  nodeId(nodeId: (node: SankeyNode<WithPosition, N, L>) => string | number): this;

  /**
   * Return the current node alignment method, which defaults to d3.sankeyJustify.
   */
  nodeAlign(): (node: SankeyNode<WithPosition, N, L>, n: number) => number;
  /**
   * Set the node alignment method the specified function and return this Sankey layout generator.
   *
   * @param nodeAlign A node alignment function which is evaluated for each input node in order,
   * being passed the current node and the total depth n of the graph (one plus the maximum node.depth),
   * and must return an integer between 0 and n - 1 that indicates the desired horizontal position of the node in the generated Sankey diagram.
   */
  nodeAlign(nodeAlign: (node: SankeyNode<WithPosition, N, L>, n: number) => number): this;

  /**
   * Return the current node width, which defaults to 24.
   */
  nodeWidth(): number;
  /**
   * Set the node width to the specified number and return this Sankey layout generator.
   *
   * @param width Width of node in pixels, which defaults to 24.
   */
  nodeWidth(width: number): this;

  /**
   * Return the current node padding, which defaults to 8.
   *
   * Node padding refers to the vertical space between nodes which occupy the same horizontal space.
   */
  nodePadding(): number;
  /**
   * Set the vertical separation between nodes at each column to the specified number and return this Sankey layout generator.
   *
   * @param padding Node padding, i.e. vertical separation between nodes at each column, in pixels, which defaults to 8.
   */
  nodePadding(padding: number): this;

  /**
   * Return the current extent which defaults to [[0, 0], [1, 1]].
   */
  extent(): [[number, number], [number, number]];
  /**
   * Set the extent of the Sankey layout to the specified bounds and returns this Sankey layout generator.
   *
   * @param extent Extent bounds for the layout. The extent bounds are specified as an array [[x0, y0], [x1, y1]],
   * where x0 is the left side of the extent, y0 is the top, x1 is the right and y1 is the bottom. The default is [[0, 0], [1, 1]].
   */
  extent(extent: [[number, number], [number, number]]): this;

  /**
   * Return the current layout size in pixels. The size is a two element array of [width, height] which defaults to [1, 1].
   */
  size(): [number, number];
  /**
   * Set the size of the layout and return this Sankey layout generator.
   * This convenience method is equivalent to using extent([[0, 0], [width, height]]).
   *
   * @param size A two element array of [width, height] in pixels which defaults to [1, 1].
   */
  size(size: [number, number]): this;

  /**
   * Return the current number of relaxation iterations, which defaults to 32.
   */
  iterations(): number;
  /**
   * Set the number of relaxation iterations when generating the layout and return this Sankey layout generator.
   *
   * @param iterations Number of relaxation iterations, which defaults to 32.
   */
  iterations(iterations: number): this;

  /**
   * Returns the node comparison function which defaults to undefined.
   */
  nodeSort():
    | ((a: SankeyNode<WithPosition, N, L>, b: SankeyNode<WithPosition, N, L>) => number)
    | undefined
    | null;

  /**
   * Set the node comparison function and return this Sankey layout generator.
   *
   * @param compare Node comparison function. If `null`, the order is fixed by the input.
   */
  nodeSort(
    compare:
      | ((a: SankeyNode<WithPosition, N, L>, b: SankeyNode<WithPosition, N, L>) => number)
      | undefined
      | null,
  ): this;

  /**
   * Returns the link comparison function which defaults to undefined.
   */
  linkSort():
    | ((a: SankeyLink<WithPosition, N, L>, b: SankeyLink<WithPosition, N, L>) => number)
    | undefined
    | null;

  /**
   * Set the link comparison function and return this Sankey layout generator.
   *
   * @param compare Link comparison function. If `null`, the order is fixed by the input.
   */
  linkSort(
    compare:
      | ((a: SankeyLink<WithPosition, N, L>, b: SankeyLink<WithPosition, N, L>) => number)
      | undefined
      | null,
  ): this;
}
